<template>
	<n-layout content-class="flex flex-col gap-2 h-full" class="p-2 h-full" :native-scrollbar="false">
		<n-text class="text-2xl">接收工程遥测
			<n-text depth="3">(停止接收时, 请打开[串口设置]页面, 直接关闭掉串口即可!)</n-text>
		</n-text>
		<n-divider class="my-1"/>
		<n-flex :wrap="false" class="h-9/11">
			<!--左区-->
			<n-flex vertical class="w-1/2">
				<n-text class="text-base">自动下载(定时存储):</n-text>
				<AutoRecv :opticalTab="opticalTab" :scdTab="scdTab" :notification="notification"/>
			</n-flex>

			<!--右区-->
			<n-flex class="w-1/2 h-full">
				<n-flex align="center">
					<n-flex align="center">
						<n-text>文件名称:</n-text>
						<n-input class="w-40" v-model:value="suggestedName">
							<template #suffix>
								<n-text depth="3">.bin</n-text>
							</template>
						</n-input>
						<n-text>接收延时:</n-text>
						<n-input-number :min="0.001" :max="1" v-model:value="delay"/>
					</n-flex>

					<n-button @click="requestSaveFile" type="success">选择文件保存(点击开始接收)</n-button>
					<n-button @click="logBuffer.splice(0,logBuffer.length)" type="error">清空日志</n-button>
					<n-button @click="() => console.log(scdTab)">print</n-button>
				</n-flex>

				<n-virtual-list ref="virtualListInst"
								class="bg-zinc-800 p-2 h-full"
								:item-size="2"
								item-resizable
								:items="logBuffer">
					<template #default="{ item, index }">
						<div :key="item.key">
							<n-text class="w-fit font-mono text-base">
								{{ item.content.toUpperCase() }}
							</n-text>
						</div>
					</template>
				</n-virtual-list>

			</n-flex>
		</n-flex>
	</n-layout>
</template>

<script setup>
import {onMounted, ref, computed, watchEffect, nextTick, reactive} from 'vue'
import {useUserProfile} from "@/stores/useUserProfile"
import {storeToRefs} from "pinia";
import {generateRandomKey} from "@/utils/byteParser"
import {uint8ArrayToHexString} from "@/utils/byteParser"
import {useNotification} from "naive-ui";
import AutoRecv from "@/views/EngineerData/Components/AutoRecv.vue";

const userProfile = useUserProfile()
const notification = useNotification()
const {profile} = storeToRefs(userProfile);
const checkedValue = ref(null)

// 日志dom元素
const virtualListInst = ref(null)
// 工程数据页
const scdTab = computed(() => profile.value.tabList.find(tab => tab.asLabel === "captureData"))
const opticalTab = computed(() => profile.value.tabList.find(tab => tab.asLabel === "optical"))
const suggestedName = ref("data")
let reader;
const delay = ref(0.01)

// 日志内容
const logBuffer = reactive([]); // 缓冲区存储日志数据

/**
 * 读取串口数据,直到最后一帧(5s内无新数据视为最后一帧)
 * @param handleChunk  处理数据段的回调
 */
async function readSerialData(handleChunk) {
	let firstFrameReceived = false;  // 用于标记是否接收到第一帧
	let timeoutDuration = 5000;  // 设置第二帧的超时时间（例如5秒）
	let timeoutId;
	let finished = false

	// 此处接收不到scdTab
	while (scdTab.value.devReadable && scdTab.value.connected && !finished) {
		scdTab.value.devReader = scdTab.value.devReadable.getReader()
		try {
			while (true) {
				const {value, done} = await scdTab.value.devReader.read()
				if (done) {
					console.log("读取完成或停止标志已触发");
					finished = true
					break;
				}
				if (!firstFrameReceived) {
					// 无限等待第一帧
					firstFrameReceived = true;
					console.log("接收到第一帧，开始接收后续帧");
				} else {
					// 对第二帧设置超时机制, 后期可绑定字节数比对
					if (timeoutId) {
						clearTimeout(timeoutId);  // 清除前一个超时
					}
					timeoutId = setTimeout(async () => {
						console.log("第二帧超时，结束读取");

						notification.warning({title: "结束读取"})
						await scdTab.value?.appReader?.cancel();  // 取消读取
						await scdTab.value?.devReader?.cancel();  // 取消读取

					}, timeoutDuration);
				}

				// 更新字符串
				logBuffer.push({
					key: generateRandomKey(),
					content: uint8ArrayToHexString(value)
				})
				// 处理写入块
				await handleChunk(value);
				await new Promise(resolve => setTimeout(resolve, delay.value));
			}
		} catch
			(error) {
			console.error(error)
		} finally {
			console.log("释放读取器");
			scdTab.value.devReader.releaseLock()
			scdTab.value.devReader = null
		}
	}
	clearTimeout(timeoutId);  // 清除前一个超时
	scdTab.value.port.readable && await scdTab.value.port.close()
	console.log("关闭了串口")
}

async function requestSaveFile() {
	// 打开文件保存句柄
	const fileHandle = await showSaveFilePicker({
		suggestedName: suggestedName.value + ".bin",
		types: [
			{
				description: 'Text Files',
				accept: {'text/plain': ['.txt']},
			},
			{
				description: 'Binary Files',
				accept: {'application/octet-stream': ['.bin']},
			},
		],
	});
	const writer = await fileHandle.createWritable();

	// 定义处理数据块的函数
	async function handleChunk(buffer) {
		// 将数据块直接写入文件
		await writer.write(buffer);
	}

	notification.success({title: "开始接收工程数据..."})
	// 从串口读取数据并处理
	await readSerialData(handleChunk);


	// 完成后关闭文件
	await writer.close();
	notification.success({title: "文件已保存", content: new Date().toLocaleString()});
}

onMounted(() => {
	// 根据logBuffer的变动刷新滚动条
	watchEffect(() => {
		if (logBuffer.length) {
			nextTick(() => {
				virtualListInst.value?.scrollTo({position: 'bottom'})
			})
		}
	})
})

</script>
<style scoped>
.item {
	display: flex;
	align-items: flex-start;
}
</style>